#pragma
#include <iostream>
#include <string>
#include <mutex>
#include <vector>
#include <fstream>
#include <cassert>
#include <jsoncpp/json/json.h>
#include "model.hpp"
#include "view.hpp"
#include "../httplib/cpp-httplib-v0.7.15/httplib.h"

using namespace std;
using namespace ns_commfunc;
using namespace ns_model;
using namespace ns_view;
using namespace httplib;

namespace ns_control
{

    class Machine
    {
    public:
        string _ip;
        uint16_t _port;
        uint64_t _load;
        mutex *_mtx; // 有可能多个用户同时访问一个主机，这个锁是为load负载数加的

        Machine() : _ip("0"), _port(0), _load(0), _mtx(nullptr)
        {
        }

        Machine(string &ip, uint16_t port, uint64_t load, mutex *mtx)
            : _ip(ip), _port(port), _load(load), _mtx(mtx)
        {
        }

        void Incode()
        {
            if (_mtx)
                _mtx->lock();
            ++_load;
            if (_mtx)
                _mtx->unlock();
        }

        void Decode()
        {
            if (_mtx)
                _mtx->lock();
            --_load;
            if (_mtx)
                _mtx->unlock();
        }

        //_mutex判空，有意义。如果为空，最多也就是load没有被保护，但一般来说_mtx不为空
        // 但这个函数意义不大
        int Getcode()
        {
            if (_mtx)
                _mtx->lock();
            int temp = _load;
            if (_mtx)
                _mtx->unlock();

            return temp;
        }

        void ResetLoad()
        {
            if (_mtx)
                _mtx->lock();
            _load = 0;
            if (_mtx)
                _mtx->unlock();
        }

        ~Machine()
        {
            // delete _mtx;
        }
    };

    const string conf_path = "./conf/service_machine.conf";

    class LoadBlance
    {
    private:
        vector<Machine> vm;
        vector<int> online;  // 存放在线主机id
        vector<int> offline; // 存放离线主机id
        mutex mtx;           // 有可能多个用户同时访问，同时有主机的上下线问题，用于保护online和offline
                             // 同时访问，有可能一个用户访问失败，主机下线，但是另一个用户同时访问，也会失败，如果很多用户
                             // 同时访问，都会访问失败，如果进行保护，那么就会访问其他主机。

    public:
        LoadBlance()
        {
            assert(LoadConf());
        }

        ~LoadBlance()
        {
        }

        // 加载主机信息
        bool LoadConf()
        {
            ifstream in(conf_path);
            if (!in.is_open())
            {
                Log(Fatal, "loadConf fail");
                return false;
            }

            string line;
            while (getline(in, line))
            {
                vector<string> vs;
                StringUtil::SplitStr(line, vs, ":");
                if (vs.size() != 2)
                {
                    Log(Warning, "line cut fail");
                    continue;
                }

                online.push_back(vm.size());
                vm.push_back({vs[0], stoi(vs[1]), 0, new mutex()});
            }

            in.close();
            return true;
        }

        // 选择主机, 1. 随机数 + hash 2. 轮询(use)
        bool SmartService(int &id, Machine *&mac)
        {
            mtx.lock(); // 这个加锁是为了保护online，因为访问他们就要访问他们的下标，而下标是临界资源

            if (online.size() == 0)
            {
                Log(Fatal, "all machine offline, please online them");
                return false;
            }

            id = online[0];
            mac = &vm[online[0]];

            int min_load = vm[online[0]].Getcode();
            for (int i = 1; i < online.size(); i++)
            {
                if (min_load > vm[online[i]].Getcode())
                {
                    min_load = vm[online[i]].Getcode();
                    id = online[i];
                    mac = &vm[online[i]];
                }
            }

            mtx.unlock();
            return true;
        }

        void Offline(int id)
        {
            mtx.lock();

            for (auto iter = online.begin(); iter != online.end(); iter++)
            {
                if (*iter == id)
                {
                    vm[id].ResetLoad();
                    online.erase(iter);
                    offline.push_back(id);
                    break;
                }
            }

            mtx.unlock();
        }

        void Showlines()
        {
            mtx.lock();

            std::cout << "当前在线主机列表: ";
            for (auto &id : online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            std::cout << "当前离线主机列表: ";
            for (auto &id : offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            mtx.unlock();
        }

        void Online()
        {
            mtx.lock();

            online.insert(online.end(), offline.begin(), offline.end());
            offline.erase(offline.begin(), offline.end());

            Log(Info, "主机全部上线");
            mtx.unlock();
        }
    };

    class Control
    {
    private:
        model _model;
        View _view;
        LoadBlance load;

    public:
        Control()
        {
        }

        ~Control() {}

        void Online()
        {
            load.Online();
        }

        bool GetQuestionList(string &out_html)
        {
            vector<Question> vsq;

            bool ret = _model.GetQuestions(vsq);
            if (ret)
            {
                // 通过view.hpp将题库转为html
                _view.ExpandAllToHtml(out_html, vsq);
            }
            else
            {
                return false;
            }

            return true;
        }

        bool GetOneQuestion(string id, string &out_html)
        {
            Question q;
            bool ret = _model.GetOneQuestion(id, q);
            // std::cout <<q.number << std::endl;
            // std::cout << q.star << std::endl;
            // std::cout <<q.title << std::endl;
            // std::cout <<q.desc << std::endl;
            // std::cout <<q.header << std::endl;
            if (ret)
            {
                // 通过view.hpp将题库转为html
                _view.ExpandOneToHtml(out_html, q);
            }
            else
            {
                return false;
            }

            return true;
        }

        void Judge(string &number, const string &in_json, string &out_json)
        {
            // 0.通过number获取需要判断的题目
            Question q;
            _model.GetOneQuestion(number, q);

            // 1.将in_json反序列化，来自于请求中的body
            Json::Value root;
            Json::Reader reader;
            Json::FastWriter writer;

            reader.parse(in_json, root); // 将in_json反序列化到root中

            // 2.将代码进行拼接，并转成json串
            Json::Value ret;
            ret["code"] = root["code"].asString() + "\n" + q.tail;
            ret["input"] = root["input"];
            ret["cpu_limit"] = q.cpu_limit;
            ret["mem_limit"] = q.mem_limit;

            string code = writer.write(ret);
            // 3.接下来进行负载均衡选择主机
            while (true)
            {
                int id;
                Machine *mac;

                bool ret = load.SmartService(id, mac);
                if (!ret)
                {
                    break;
                }

                Client cli(mac->_ip, mac->_port);
                mac->Incode();
                auto resp = cli.Post("/compile_run", code, "application/json; charset=utf-8");

                Log(Info, "选择主机成功，主机号为%d, 主机详情 %s:%d", id, mac->_ip.c_str(), mac->_port);
                // 返回值为来自compiler的应答，我们获取他的body，json串直接返回。
                if (resp)
                {
                    out_json = resp->body;
                    mac->Decode();

                    Log(Info, "请求和编译服务运行成功");
                    break;
                }
                else
                {
                    Log(ERROR, "当前请求的主机%d可能已经离线 详情查看: %s:%d", id, mac->_ip.c_str(), mac->_port);
                    load.Offline(id);
                    load.Showlines();
                }
            }
        }
    };

}
